<?php

/** Generic functions & classes
 *
 *  This collection of functions (libary) is licensed under GPL2.
 *  See LICENSE or www.gnu.org for more details.
 *  @author Jyry Kuukkanen
 *  @copyright Jyry Kuukkanen
 *
 *  $Id: sogeneric.php,v 1.21 2005/11/09 19:01:47 jyry Exp $
 */

include_once("sostring.php");

/* Miscalenious */
define(SO_DIGITS, "1234567890");     /** number characters */

/* URL en/decoding */
define(SO_URL_ARRAY_DELIM, chr(12)); /** delimiter char to delimit array
                                      *  elements */

/* Domain sufficies */
define(DOMAIN_SUFFICIES, 
       'ad|ae|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|at|au|aw|az|ba|bb|bd|be|bf|'.
       'bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|'.
       'cm|cn|co|com|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|eh|er|'.
       'es|et|fi|fj|fk|fm|fo|fr|fx|ga|gb|gov|gd|ge|gf|gh|gi|gl|gm|gn|gp|gq|'.
       'gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|in|int|io|iq|ir|is|it|'.
       'jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|'.
       'lv|ly|ma|mc|md|mg|mh|mil|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|'.
       'my|mz|na|nato|nc|ne|net|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|'.
       'pg|ph|pk|pl|pm|pn|pr|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|'.
       'sj|sk|sl|sm|sn|so|sr|st|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|'.
       'tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|'.
       'za|zm|zw');


/** Inserts an element into an array.
 *  Inserts an element before item at $Pos, or to the end (-1, default).
 *  @param array $inArray Array to insert a new value to
 *  @param any $Item Item to insert into the array.
 *  @param int $Pos Position to insert at.
 *  @return int Returns true(1) on success, on error returns false(0) such as
 *      $Pos > itemcount or < -1).
 *  @package sogeneric
 */
function soInsElement(&$InArray, $Item, $Pos = -1) {
    /* Range check */
    $icount = count($InArray);
    if ($Pos == -1) $Pos = $icount;
    if (($Pos > $icount) || ($Pos < 0)) return 0; // <-- EXIT

    /* Only value into an empty array */
    if ($icount == 0) {
        $InArray = array($Item);
    } else {
        if ($Pos == $icount) {
            array_push($InArray, $Item);
        } else {
            array_splice($InArray, $Pos, 0, $Item);
        }; // else if
    }; // else if

    return 1;
} // soInsElement

/** Deletes an element from an array
 *  @param array $InArray AN array to delete from.
 *  @param int pos Position of an element in the array to delete. negative
 *  values are valid and causes to count the position from the end of an array
 *  toward the begining, so that -1 == the last element, -2 == 2cd to last etc.
 *  @return bool Returns 1 on success, otherwise 0 (Pos out of range, or the
 *  array is not really an array)
 */
function soDelElement(&$InArray, $Pos) {
    if (!is_array($InArray)) return 0; // <-- EXIT

    /* Range check */
    $icount = count($InArray);
    if ($Pos < 0) $Pos = $icount +$Pos;
    if (($Pos < 0) || ($Pos >= $icount)) return 0; // <-- EXIT

    array_splice($InArray, $Pos, 1);

    return 1;
}; // soDelElement


/** Adds a value to the end of an array.
 *  This fixes the PHP limitation of using $array["some"][] = "x" notation.
 *  @param array $Array Array to add a value to.
 *  @param any $Value Value to push to the end of the array.
 *  @return array/any Returns the input array with the value added to the end.
 *  @package sogeneric
 */
function soArrayPush(&$OldArray, $NewValue) {
    $result = $OldArray[] = $NewValue;
    return $result;
}; // soArrayPush


/** Gets an item from an array.
 *  This function works as $var[$x] notation in php, but as php does not
 *      understand func_call_that_returns_an_array()[$x] notation, then this
 *      can be used instead. See examples for more.
 *  Example:  func_that_returns_an_array[0]) would cause run-time error
 *  Example:  soGetArrayItem(func_that_returns_an_array, 0) would run fine
 *  Example:  soGetArrayItem(func_that_returns_an_array, "aa") would run fine
 *  @param array/any $InArray Input array
 *  @param int/string $Key Item position or name that refers to item.
 *  @return any Returns an item in the array or NULL if Key refers to non-existing
 *      item in the array.
 *  @package sogeneric
 */
function soGetArrayItem(&$InArray, $Key) {
        $keys = array_keys($InArray);
        $pos = soArrayFind($keys, $Key, 1);
        if ($pos == -1) return NULL;

        return $InArray[$keys[$pos]];
}; // soGetArrayItem


/** No. of elements in an array (see details).
 *  Fixed version of php's count() function. This returns the number of
 *      elements in an array, if an array. Otherwise returns 0.
 *  @param any $InArray Input array to return a count of.
 *  @return int NumNo. of elements in the input array. If the input value is
 *      not an array, then 0 (nough) is returned.
 *  @package sogeneric
 */
function soArrayCount($InArray) {
    if (is_array($InArray)) {
        return count($InArray);
    } else {
        return 0;
    };
}; // soArrayCount


/** Compares two arrays.
 *  Compares two arrays and returns an array of keys that differs/are same
 *      from each other. This does not work very well with associative arrays,
 *      especially if thay are of different size.
 *  Example: soCompArray(array("a", "b", "c"), array("b", "b", "b")) would
 *      return array(0, 2).
 *  Example: soCompArray(array("a, "b", "c"), array("b", "b", "b"), true)
 *      would return array(1) (only 2nd elements are the same)
 *  @param array/any $Array1 1st array
 *  @param array/any $Array2 2nd array
 *  @param bool $Duplicate When set, returns keys of values that are the same,
 *      not different.
 *  @return array/any Returns an array of keys that differs from each other
 *      or are equal ($Duplicate set).
 *  @package sogeneric
 */
function soCompArrays($Array1, $Array2, $Duplicate = 0) {
    $result = array();

    $keys = array_keys($Array1);
    $keys2 = array_keys($Array2);
    if (count($keys) < count($keys2)) {
        $tmp_keys = $keys2;
        $keys2 = $keys;
        $keys = $tmp_keys;
        $tmp_keys = NULL;
    };

    /* v-- What is this, really? --jq */
    if ($keys != $keys2) $keys = array_merge($Array1, $Array2);

    if (!count($keys)) return $result; // <-- EXIT

    foreach ($keys as $key) {
        if ((($Array1[$key] == $Array2[$key]) && ($Duplicate)) ||
            (($Array1[$key] != $Array2[$key]) && (!$Duplicate))) {
                $result[] = $key;
        }; // if
    } // foreach

    return $result;
}; // soCompArrays


/** Returns an array of values in common or different in both arrays.
 *  Example: soCompArrayValues(array(1, 2, 3), array(1, 3, 5, 7), 1) - get
 *  values in common - would return array(1, 3) where as the same with last
 *  parameter set to 0 - find values not in common - would return 
 *  array(2, 5, 7). The optional parameter Method can be set to 1 or 2 to 
 *  specify that only the first array should be compared to second, or second to
 *  first.
 *  @param array/any $Array1 First array to compare.
 *  @param array/any $Array2 Second array to compare.
 *  @param bool $Equals when set, returns values in common in both arrays,
 *  otherwise returns those values not in common.
 *  @param int $Method 1=only compare A1 to A2, 2=only compare A2 to A1,
 *  0=Compare arrays both ways.
 *  @return array/any Values in common or that differs in the two arrays, or
 *  NULL when arrays passed are not really arrays.
 */
function soCompArrayValues($Array1, $Array2, $Equals, $Method = 0) {
    if ((!is_array($Array1)) ||  (!is_array($Array1))) return NULL; // <-- EXIT

    if (!$Method) {
        /* When comparing both ways, we just call ourself twice with both
         * 1-2 and 2-1 methods and return the merged results. */
        
        return soMergeArrays(
            soCompArrayValues($Array1, $Array2, $Equals, 1),
            soCompArrayValues($Array1, $Array2, $Equals, 2), 0); // <-- EXIT
    }; // if

    /* The actual comparison routine starts here */
    if ($Method == 1) {
        $arrs = array($Array1, $Array2);
    } else {
        $arrs = array($Array2, $Array1);
    };

    /* Have to use array keys in case of associated arrays. */
    $keys = array_keys($arrs[0]);
    $result = array();
    foreach ($keys as $key) {
        $value = $arrs[0][$key];
        if ($Equals == in_array($value, $arrs[1])) $result[] = $value;
    }; // foreach

    return $result;
}; // soCompArrayValues

/** Merges two arrays either by removing doubles or just appending.
 *  Example soMergeArrays(array(1, 2), array(2, 3), 0) would return 
 *  array(1, 2, 3) and when Append is set to 1, array(1, 2, 2, 3) would be
 *  returned.
 *  @param array $Array1 An array to be merged with another
 *  @param array $Array2 An array to be merged with another
 *  @param bool $Append When set, tries appending the other array to the end of
 *  the previous by calling PHP's array_merge, otherwhise merges the arrays
 *  by removing duplicate values from the resulting array.
 *  @return array The two arrays merged.
 */
function soMergeArrays($Array1, $Array2, $Append) {
    if ($Append) return array_merge($Array1, $Array2); // <-- EXIT

    /* Actual merging starts here */
    $result = $Array1;
    foreach ($Array2 as $value) {
        if (!in_array($value, $result)) $result[] = $value;
    }; // foreach

    return $result;
}; // soMergeArrays


/** Convert array name keys to numbers.
 *  Returns the input array with all keys converted to sequential numbers.
 *  Example:  soForceNumKeyArray(array("a" => "x", "b" => "y")) woud return the same
 *      array but with keys 0 and 1 instead of "a" and "b".
 *  @param array/any $InArray Array to convert.
 *  @return array/any Returns the converted array.
 *  @package sogeneric
 */
function soForceNumKeyArray($NameArray) {
    $result = array();
    $keys = array_keys($NameArray);
    foreach ($keys as $key) {
        $result[] = $NameArray[$key];
    }; // foreach key

    return $result;
}; // soForceNumKeyArray


/** Searches an array for a value.
 *  Looks for specified occurance of a value in an array and returns it's
 *      index no.
 *  N.B. return value is 0 based (0=1st index no.)
 *  BUT occurance is 1 based (1st occurance, 2nd, ...)
 *  Example: soArrayFind(array("a", "b", "c", "d"), "c", 1) would return 2
 *  Example: soArrayFind(array("a", "q", "c", "q"), "q", 2) would return 3
 *  @param array $InArray Array to search from.
 *  @param array $InValue Value to search for.
 *  @param int $Occurance Which occurance of the value to look for.
 *  @return int Returns the index no. of found value or -1 when not found.
 *  @package sogeneric
 */
function soArrayFind(&$InArray, $InValue, $Occurance = 1) {
    /* Check and exit if an empty input array */
    if (!count($InArray)) return -1; // <-- EXIT

    /* loop until end if $InArray or $value found (case or incase)
     * for the $Occurance'th time.
     */
    $ocount = 0;
    $keys = array_keys($InArray);
    $acount = count($keys);
    $a = -1;
    while ((++$a < $acount) && ($ocount < $Occurance)) {
        if ($InValue == $InArray[$keys[$a]]) $ocount++;
    }; // while

    if ($ocount != $Occurance) $a = 0;

    return --$a;
}; // soArrayFind


/** Searches an array for a value - similar to soArrayFind
 *  Looks for specified occurance of a value in an array and returns it's
 *      key value, no matter numeric or associative array.
 *  N.B. Occurance is 1 based (1st occurance, 2nd, ...)
 *  Example: soArrayFind(array("a", "b", "c", "d"), "c", 1) would return 2
 *  Example: soArrayFind(array("a" => "b", "q" => "c", "c", 2) would return "q"
 *  @param array $InArray Array to search from.
 *  @param array $InValue Value to search for.
 *  @param int $Occurance Which occurance of the value to look for.
 *  @return int Returns the index no. of found value or -1 when not found.
 *  @package sogeneric
 */
function soArraySearch(&$InArray, $InValue, $Occurance = 1) {
    $result = soArrayFind($InArray, $InValue, $Occurance);
    if ($result == -1) {
        $result = NULL;
    } else {
        $keys = array_keys($inArray);
        $result = $keys[$result];
    }; // else if

    return $result;
}; // soArraySearch


/** Same as soArrayFind but is case-insensitive
 *  @param array $InArray Array to search from.
 *  @param array $InValue Value to search for.
 *  @param int $Occurance Which occurance of the value to look for.
 *  @return int Returns the index no. of found value or -1 when not found.
 *  @package sogeneric
 */
function sociArrayFind($InArray, $InValue, $Occurance = 1) {
    $in_array = array();
    $icount = count($InArray);
    for ($i = 0; $i < $icount; $i++) {
        $in_array = soCaseConv($InArray[$i], "U");
    }; // for $i

    return soArrayFind($in_array, soCaseConv($InValue, "U"), $Occurance);
}; // sociArrayFind


/** Check whether any values in one array is found in another.
 *  Looks for specified values whether any of them is found in another array.
 *  Example: soArrayMultiFind(array("a", "b", "c", "d"), array("c", "a), 1) would return 2
 *  Example: soArrayFind(array("a", "q", "c", "q"), "q", 2) would return 3
 *  @param array $InArray Array to search from.
 *  @param array $InValues Values to search for.
 *  @return int Returns true(1) when any value was found, otherwise false(0).
 *  @package sogeneric
 */
function soInArray(&$InArray, $InValues) {
    /* Check and exit if an empty input array or non array parameter */
    if ((!is_array($InValues)) ||
        (!is_array($InArray)) ||
        (!count($InArray)) ||
        (!count($InValues))) {
        return 0; // <-- EXIT
    }; // if

    /* loop until end if $InArray or $value found (case or incase)
     * for the $Occurance'th time.
     */
    $result = 0;
    $i = count($InValues) -1;
    while (($i >= 0) && (!$result))  {
        $result = in_array($InValues[$i--], $InArray);
    }; // while

    return $result;
}; // soInArray


/** Dumps an array as a string.
 *  Dumps out an array of any dimension, so that each array containing an
 *      array gets dumped too.
 *  @param array/any $InArray Array to dump.
 *  @param string $ArrayDelim String to delimit one array from another
 *      (sub-array)
 *  @param string $ItemDelim String to delimit values in one array.
 *  @param string $EqualSign String to delimit key from the value.
 *  @param string $Quote String to enclose the value in (key='value')
 *  @param string $Prefix This is used by the function itself, when iterating
 *      through sub-arrays.
 *  @return string Returns the dumped array(s) as a string.
 *  @package sogeneric
 */
function soDumpArray(&$InArray, $ArrayDelim = "\n", $ItemDelim = ", ",
                     $EqualSign = " = ", $Quote = "'", $Prefix = "") {
    if (!count($InArray)) return "an empty array".$ArrayDelim; // <-- EXIT

    $result = "";
    $keys = array_keys($InArray);
    if (count($keys) > 0) {
        foreach ($keys as $key) {
                $item = $InArray[$key];
                if (is_array($item)) {
                    $result .= soDumpArray($item, $ArrayDelim, $ItemDelim,
                                           $EqualSign, $Quote, $key);
                } else {
                    $result = soJoinStr($result, $ItemDelim,
                                        $key.$EqualSign.$Quote.
                                        $item.$Quote, 1);
                };
        }; // foreach $key
    } else {
            $result = "an empty array";
    }; // else if count($keys)

    if ($Prefix) $result = $Prefix."/".$result;
    return $result.$ArrayDelim;
}; // soDumpArray


/** Tests whether specified value is numeric value (int or float).
 *  @param any $Value Value to tested.
 *  @return bool True if input value is of type int or float.
 *  @package sogeneric
 */
function soIsNum($Value) {
    if (soIsNull($Value) || 
        ((gettype($Value) == "string") && $Value == "")) return 0; // <-- EXIT

    /* Remove possible negation symbol from the front. Rest of the string
     * should contain plain digits and max one "." symbol. */
    if (substr($Value, 0, 1) == "-") $Value = substr($Value, 1);

    // For some reason PHP considers some numeric values as "string",
    // so the test below cannot be used.
    // if (gettype($Value) == "string") return 0; // <-- EXIT

    $result = 1;
    $points = 0;
    $i = 0;
    $icount = strlen($Value);
    while (($result) && ($i < $icount)) {
        $char = substr($Value, $i, 1);
        if ($char == ".") $point++;
        if ((soStrPos(SO_DIGITS, $char) == -1) &&
            (($char != ".") || ($points > 1))) $result = 0;
        $i++;
    }; // while

    return $result;
}; // soIsNum


/** Compares a value to null string ""/NULL
 *  This function fixes strange behaviour in php when comparing "" to numeric
 *  value. Php returns true on 0 == "" situations which is not really
 *  appreciated in most of the cases.
 *  @param any $Value Any value to perform the NULL test on.
 *  @return bool Returns true(1) if the value is truly NULL, otherwise false(0)
 *  @package sogeneric
 */
function soIsNull($Value) {
    /* The 1st condition is for "Apache 1.3.x+PHP 4.x" combination and the
     * second for "Apache 2.x+PHP 4.2". For some strange reason, the NULL
     * handling/reporting is not the same with Apache 1.3 and 2 when it
     * comes to PHP module. Nice. */
    if ((gettype($Value) == "NULL") ||
        ((gettype($Value) == "boolean") && ($Value == NULL))) {
        return 1;
    } else {
        return 0;
    };
}; // soIsNull


/** Cunning piece of code to get around shitty PHP null/""/0 handling.
 *  @param any $Value Any freaking value to test for ""
 *  @return bool Returns true(1) if $Value contain an EMPTY STRING, otherwise
 *      returns false(0).
 */
function soIsEmptyStr($Value) {
    if (soIsNum($Value) || soIsNull($Value) || ($Value != "")) {
        return 0;
    } else {
        return 1;
    };
}; // soIsEmptyStr


/** Gets boolean 1 or 0 for any value
 *  Fixes PHP feature that converts false to NULL. This returs always
 *  the input value or 0 for NULL
 *  @param any $Value Any input value
 *  @return any Returns input value or 0 for NULL value.
 *  @package sogeneric
 */
function soGetBool($Value) {
    if (($Value == NULL) || ($Value == "")) $Value = 0;
    return $Value;
}; // 


/** Encodes an array to a URL style string
 *  This is a binary safe array encoding function.
 *  @param array/any $InArray Input array to encode (binany data ok)
 *  @return string Returns an URL encoded string
 */
function soUrlEncodeArray($InArray) {
    if ((!is_array($InArray)) || (!count($InArray))) return NULL; // <-- EXIT

    $result = "";
    foreach ($InArray as $item) {
        $result = soJoinStr($result, SO_URL_ARRAY_DELIM, urlencode($item), 1);
    }; // foreach

    return urlencode($result);
}; // soUrlEncodeArray


/** Decodes an URL string into a array
 *  This is a binary safe array decoding function.
 *  @param string $UrlString Input array to decode
 *  @return array/any Returns an array of values from URL encoded string
 *  @package sogeneric
 */
function soUrlDecodeArray($UrlString) {
    $result = soExplode(urldecode($UrlString), SO_URL_ARRAY_DELIM);
    $icount = count($result);
    for ($i = 0; $i < $icount; $i++) {
        $result[$i] = urldecode($result[$i]);
    }; // for

    return $result;
}; // soUrlDecodeArray


/** Returns "true" or "false" for a bool value.
 *  @param any $Value Any value to get a bool string for.
 *  @return string Returns "true" or "false"
 *  @package sogeneric
 */
function soGetBoolStr($Value) {
    if ($Value) {
        $result = "true";
    } else {
        $result = "false";
    };

    return $result;
}; // soGetBoolStr


/** Get a boolean value for a string
 *  Returns given value as true or false (1 or 0).
 *  Unkwon values are returned as 0 (false(, unless $Strict is set when
 *  unknown values return -1.
 *  Example:  soGetBoolValue("ab", 1) would return -1 (strict set)
 *  Example:  soGetBoolValue("ab", 0) would return 1 (strict not set)
 *  Example:  soGetBoolValue("false") would return 0
 *  @param any $Value Value.
 *  @param bool $Strict When set, returns -1 for unknown input values.
 *  @return string Returns a value for specified section and key.
 *  @package sogeneric
 */
function soGetBoolValue($Value, $Strict = 0) {
    switch (strtolower($Value)) {
        case "y":
        case "yes":
        case "true":
        case "on":
        case "1":
        case 1:
            $result = 1;
            break;
        case "n":
        case "no":
        case "false":
        case "off":
        case "0":
        case "":
        case NULL:
            $result = 0;
            break;
        default:
            $Strict ? $result = -1 : $result = 1;
    }; // switch

    return $result;
} // soGetBoolValue


/** Type casts a value and returns it
 *  @param any $Value Value to get in certain datatype
 *  @param string $AsType One of "any", "bool" (returns 1/0), "int" (intval) or
 *  "dec" (doubleval).
 *  @return any Returns the value in specified form (int/dec/...)
 *  @package sogeneric
 */
function soGetAsType($Value, $AsType = "any") {
    switch ($AsType) {
        case "dec":
            $result = doubleval($Value);
            break;
        case "int":
            $result = intval($Value);
            break;
        case "bool":
            $result = soGetBoolValue($Value);
            break;
        default:
            $result = $Value;
    }; // switch

    return $result;
}; // soGetAsType


/** Checks if the value is between the given range
 *  Range is defined as a string: "min,max". One element inte range array
 *  defines one range. Thus several ranges can be given. If either limit is
 *  omited, i.e. not specified, then it is considered "open". For example,
 *  "12," (ir just "12") would mean "any value that is equal or larger than 12"
 *  and ",12" would mean "any value that is equal or less than 12",
 *
 *  @param float $Value The value to check
 *  @param array/str $RangeList The range list, each element contains a
 *  space delimited lis to min and max values: "min,max".
 *  @return bool Returns last found array element no. or -1 if given value
 *  in none of the specified range or bad parameters passed.
 */
function soCheckRange($Value, $RangeList) {
    if (!is_array($RangeList)) return -1; // <-- EXIT

    $i = count($RangeList) -1;
    $result = -1;
    do {
        $min_max = soSplitStr($RangeList[$i], ",", 1);
        if (!count($min_max)) $min_max = array($value, "");

        /* Min value check */
        if ((($min_max[0] == "") && ($Value <= $min_max[1])) ||
            (($min_max[1] == "") && ($Value >= $min_max[1])) ||
            (($min_max[0] <= $Value) && ($min_max[1] >= $Value))) $result = $i;
    } while ((--$i > -1) && ($result == -1));

    return $result;
} // soCheckRange


/** Checks for double values in an array.
 *  Loops all array items and returns a list of array keys that are assigned
 *  to a value that occures in the array more than once.
 *  For example, array(1, 2, 2, 2, 3, 4, 5, 5) would return array(2, 3, 7).
 *  @param array/any $InArray Array to check.
 *  @return array/any An array of keys that contains a value encountered more
 *  than once in the same array.
 */
function soGetArrayDoubles(&$InArray) {
    $keys = array_keys($InArray);
    $tmp_array = array();
    $result = array();

    foreach ($keys as $key) {
        if (in_array($InArray[$key], $tmp_array)) {
            $result[] = $key;
        } else {
            $tmp_array[] = $InArray[$key];
        };
    }; // foreach

    return $result;
}; // soGetArrayDoubles


/** Rotates a soArraySet object 90 deg
 *  @param soArraySet $ArraySet Array set to rotate.
 *  @return soArraySet Input array set rotated.
 */
function soRotateArraySet($ArraySet) {
    $result = array();
    $keys = $ArraySet->getKeys();
    $kcount = count($keys);
    $icount = $ArraySet->getRowCount($keys, 1);
    for ($i = 0; $i < $icount; $i++) {
        $row = new soSet();;
        for ($k = 0; $k < $kcount; $k++) {
            $value = $ArraySet->getArrayItem($keys[$k], $i);
            $row->addItem($keys[$k], $value);
        }; // for $k
        $result[] = $row;
    }; // for $i

    return $result;
} // soRotateArraySet


/** Converts an CSV stream to an array.
 *  Example: soCsvToArray("file://input.csv") would return file "input.csv"
 *      as an two dim array.
 *  @param $Stream Stream for soInputStream
 *  @return array/array/any Array of array that contain all the values in CSV
 *      stream.
 */
function soCsvToArray($Stream) {
    include_once("sostreams.php");

    $result = array();
    $input = new soInputStream($Stream);
    $csv = soExplode(str_replace("\r", "",$input->Data), "\n");

    foreach ($csv as $line) {
        $items = soSplitCsvLine($line, ",", $Quote);
        preg_match_all ("/(\"[^\"]+\"|[^,]+)/", $line, $matches);
        $ccount = count($matches[0]);
        for ($c = 0; $c < $ccount; $c++) {
            $matches[0][$c] = soRemoveQuotes($matches[0][$c]);
        }; // for $c
        $result[] = $matches[0];
    }; // foreach

    return $result;
}; // soCsvToArray


/** Splits a CSV line into an array.
 *  @param $Line string CSV line to split.
 *  @param $Delim string Delimiter char/str
 *  @param $Quote string Quote character
 *  @param $RemoveQuotes bool When set, removes quotes around an item, if any.
 *  @return array/any Items as an array.
 */
function soSplitCsvLine($Line, $Delim, $Quote = "\"", $RemoveQuotes = 1) {
    $result = array();
    $item_count = 0;
    $in_quotes = false;

    for ($i = 0; $i < strlen($Line); $i++) {
        if (!isset($result[$item_count])) $result[$item_count] = "";
        $tmp = substr($Line, $i, strlen($Delim));
        if ($tmp === $Delim && !$in_quotes) {
            $item_count++;
            $i += strlen($Delim) -1;
        } else if ($result[$item_count] == "" && $Line[$i] == $Quote && 
                   !$in_quotes) {
            if (!$removeQuotes) $result[$item_count] .= $Line[$i];
            $in_quotes = true;
        } else if ($Line[$i] == $Quote) {
            if ($Line[$i +1] == $Quote) {
                $i++;
                $result[$item_count] .= $Line[$i];
            } else {
               if (!$removeQuotes) $result[$item_count] .= $Line[$i];
               $in_quotes = false;
            }; // else if 
        } else {
           $result[$item_count] .= $Line[$i];
        }; // else if
    }; // for $i

    return $result;
}; // soSplitCsvLine


/** Converts an array of "key=value" pair string to an associative array.
 *  If the string contains more than one delimiter, all but the first are 
 *      included in the actual value: "key=value=another" would result in
 *      "key" => "value=another".
 *  Example: soStrToAArray("one=1, "two=2") would return 
 *      array("one" => 1, "two" => 2).
 *  @param array/atring $InArray Key/value pairs.
 *  @param $Replace When set, overwrites any previous key values in the 
 *      resulting array, otherwise skips any double key values: "one=1", "one=2"
 *      returns either array("one" => 2) when replace is set, or 
 *      array("one" => 1) when not set.
 *  @param string $Delim Delimiter string that delimits key from value.
 *  @return array Associative array of "key" => "value" pairs or NULL when
 *      input array is not an array.
 */
function soStrToAArray($InArray, $Replace = 1, $Delim = "=") {
    if (!is_array($InArray)) return NULL; // <-- EXIT
    $result = array();
    foreach ($InArray as $pair) {
        if (soStrPos($pair, $Delim) == -1) {
            $key = $pair;
            $value = "";
        } else {
            list($key, $value) = soSplitStr($pair, $Delim, 1);
        }; // else if

        if ($Replace || !isset($result[$key])) {
            $result[$key] = $value;
        }; // if
    }; // for $i

    return $result;
}; // soStrToAArray


/** Joins two arrays as an associative array.
 *  Takes two ordered arrays and returns them back as one. The first array
 *  contains key names and the latter values.
 *  Example: soJoinKeyValueArrays(array("a", "b", "c"), array(1, 2, 3) would
 *  return array("a" => 1, "b" => 2, "c" => 3).
 *  @param array/any $KeyArray Array keys.
 *  @param array/any $ValueArray Array values.
 *  @param $Replace When set, overwrites any previous key values in the 
 *      resulting array, otherwise skips any double key values. In other words,
 *      if the key array contains same key more than once, the previous value
 *      in resulting array gets overwritten, replaced, when set.
 *  @return array/any Associative array of values input or NULL when either 
 *      array is not really an array or their count does not match.
 */
function soJoinKeyValueArrays($KeyArray, $ValueArray, $Replace = 1) {
    if ((!is_array($KeyArray)) || 
        (!is_array($ValueArray)) ||
        (count($KeyArray) != count($KeyArray))) return NULL; // <-- EXIT

    $result = array();
    $keys = array_keys($KeyArray);
    $kcount = count($KeyArray);
    for ($k = 0; $k < $kcount; $k++) {
        $key = &$KeyArray[$k];
        if (($Replace) ||
            !isset($result[$key])) {
            $result[$key] = $ValueArray[$k];
        };
    } // for $k

    return $result;
}; // soJoinKeyValueArrays


/** Splits an associative array into key and value arrays.
 *  Returns an array with two elements, array(0, 1). The first contains all
 *  the input array keys, the latter all the values.
 *  For example, soSplitAArray(array("a" => 1, "b" => 2)) would return
 *  array(array("a", "b"), array(1, 2)).
 *  @param array/any $InArray Associative array to be split.
 *  @return array/array/any The input array as an array of two elements,
 *      keys and values, or NULL when input array is not really an array.
 */
function soSplitAArray($InArray) {
    if (!is_array($InArray)) return NULL; // <-- EXIT
    
    $keys = array();
    $vals = array();
    foreach ($InArray as $key => $value) {
        $keys[] = $key;
        $vals[] = $value;
    }; // foreach

    return array($keys, $vals);
}; //soSplitAArray


/** Returns an array of seqeuence of numbers.
 *  Populates an array with sequence of numbers. Example 
 *  soGetSeqArray(1, 1, 5) would return array(1, 2, 3, 4, 5) and
 *  soGetSeqArray(4, -2, 4) would return array(4, 2, 0, -2).
 *  @param int $Start First number to appear in the array.
 *  @param int $Step How much to add to or subtract from previous array value.
 *  @param int $Count Number of elements in the return array.
 *  @return array Array of sequence of numbers.
 */
function soGetSeqArray($Start, $Step, $Count) {
    if ($Count <= 0) return array(); // <-- EXIT

    $result = array($Start);
    while (--$Count > 0) {
        $Start += $Step;
        $result[] = $Start;
    }; // while
    return $result;
}; // soGetSeqArray


/** Validates an email address
 *  @author Jyry Kuukkanen (based on a code spiplet by Derek Ford)
 *  @param string $Addr Email address to validate
 *  @return bool True for valid eMail address, otherwise false.
 */
function soIsValidEmailAddr($Addr)
{
    $str = strtolower($str);
    return ereg('^([^[:space:]]+)@(.+)\.('.DOMAIN_SUFFICIES.')$',
                strtolower($Addr));
}; // soIsValidEmailAddr


/** Gets a folder separator character based on OS
 *  This should be fixed to be more cunning than "win" test. -- JQ
 *  @author Jyry Kuukkanen (based on a code spiplet by Derek Ford)
 *  @return string "/" or "\" based on operating system the script is run at
 */
function soGetFolderDelimiter()
{
    if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
        $result = ';';
    } else {
        $result = ';';
    };
    return $result;
}; // soIsValidEmailAddr


/** Gets a class name - incase-sensitive
 *  PHP4 returns class name in all lowercase where PHP5 as is.
 *  This function helps migration code from PHP4 to PHP5
 *  @author Jyry Kuukkanen
 *  @return string Class name all lowercase.
 */
function soGetClass($ClassName)
{
    return strtolower(get_class($ClassName));
}; // soGetClass


?>
